/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.tagplugins.jstl.core;

import org.apache.jasper.compiler.tagplugin.*;

public final class ForEach implements TagPlugin {

	private boolean hasVar, hasBegin, hasEnd, hasStep;

	public void doTag(TagPluginContext ctxt) {

		String index = null;

		boolean hasVarStatus = ctxt.isAttributeSpecified("varStatus");
		if (hasVarStatus) {
			ctxt.dontUseTagPlugin();
			return;
		}

		hasVar = ctxt.isAttributeSpecified("var");
		hasBegin = ctxt.isAttributeSpecified("begin");
		hasEnd = ctxt.isAttributeSpecified("end");
		hasStep = ctxt.isAttributeSpecified("step");

		boolean hasItems = ctxt.isAttributeSpecified("items");
		if (hasItems) {
			doCollection(ctxt);
			return;
		}

		// We must have a begin and end attributes
		index = ctxt.getTemporaryVariableName();
		ctxt.generateJavaSource("for (int " + index + " = ");
		ctxt.generateAttribute("begin");
		ctxt.generateJavaSource("; " + index + " <= ");
		ctxt.generateAttribute("end");
		if (hasStep) {
			ctxt.generateJavaSource("; " + index + "+=");
			ctxt.generateAttribute("step");
			ctxt.generateJavaSource(") {");
		} else {
			ctxt.generateJavaSource("; " + index + "++) {");
		}

		// If var is specified and the body contains an EL, then sycn
		// the var attribute
		if (hasVar /* && ctxt.hasEL() */) {
			ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
			ctxt.generateAttribute("var");
			ctxt.generateJavaSource(", String.valueOf(" + index + "));");
		}
		ctxt.generateBody();
		ctxt.generateJavaSource("}");
	}

	/**
	 * Generate codes for Collections The pseudo code is:
	 */
	private void doCollection(TagPluginContext ctxt) {

		ctxt.generateImport("java.util.*");
		generateIterators(ctxt);

		String itemsV = ctxt.getTemporaryVariableName();
		ctxt.generateJavaSource("Object " + itemsV + "= ");
		ctxt.generateAttribute("items");
		ctxt.generateJavaSource(";");

		String indexV = null, beginV = null, endV = null, stepV = null;
		if (hasBegin) {
			beginV = ctxt.getTemporaryVariableName();
			ctxt.generateJavaSource("int " + beginV + " = ");
			ctxt.generateAttribute("begin");
			ctxt.generateJavaSource(";");
		}
		if (hasEnd) {
			indexV = ctxt.getTemporaryVariableName();
			ctxt.generateJavaSource("int " + indexV + " = 0;");
			endV = ctxt.getTemporaryVariableName();
			ctxt.generateJavaSource("int " + endV + " = ");
			ctxt.generateAttribute("end");
			ctxt.generateJavaSource(";");
		}
		if (hasStep) {
			stepV = ctxt.getTemporaryVariableName();
			ctxt.generateJavaSource("int " + stepV + " = ");
			ctxt.generateAttribute("step");
			ctxt.generateJavaSource(";");
		}

		String iterV = ctxt.getTemporaryVariableName();
		ctxt.generateJavaSource("Iterator " + iterV + " = null;");
		// Object[]
		ctxt.generateJavaSource("if (" + itemsV + " instanceof Object[])");
		ctxt.generateJavaSource(iterV + "=toIterator((Object[])" + itemsV
				+ ");");
		// boolean[]
		ctxt
				.generateJavaSource("else if (" + itemsV
						+ " instanceof boolean[])");
		ctxt.generateJavaSource(iterV + "=toIterator((boolean[])" + itemsV
				+ ");");
		// byte[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof byte[])");
		ctxt.generateJavaSource(iterV + "=toIterator((byte[])" + itemsV + ");");
		// char[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof char[])");
		ctxt.generateJavaSource(iterV + "=toIterator((char[])" + itemsV + ");");
		// short[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof short[])");
		ctxt
				.generateJavaSource(iterV + "=toIterator((short[])" + itemsV
						+ ");");
		// int[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof int[])");
		ctxt.generateJavaSource(iterV + "=toIterator((int[])" + itemsV + ");");
		// long[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof long[])");
		ctxt.generateJavaSource(iterV + "=toIterator((long[])" + itemsV + ");");
		// float[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof float[])");
		ctxt
				.generateJavaSource(iterV + "=toIterator((float[])" + itemsV
						+ ");");
		// double[]
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof double[])");
		ctxt.generateJavaSource(iterV + "=toIterator((double[])" + itemsV
				+ ");");

		// Collection
		ctxt.generateJavaSource("else if (" + itemsV
				+ " instanceof Collection)");
		ctxt.generateJavaSource(iterV + "=((Collection)" + itemsV
				+ ").iterator();");

		// Iterator
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof Iterator)");
		ctxt.generateJavaSource(iterV + "=(Iterator)" + itemsV + ";");

		// Enumeration
		ctxt.generateJavaSource("else if (" + itemsV
				+ " instanceof Enumeration)");
		ctxt.generateJavaSource(iterV + "=toIterator((Enumeration)" + itemsV
				+ ");");

		// Map
		ctxt.generateJavaSource("else if (" + itemsV + " instanceof Map)");
		ctxt.generateJavaSource(iterV + "=((Map)" + itemsV
				+ ").entrySet().iterator();");

		if (hasBegin) {
			String tV = ctxt.getTemporaryVariableName();
			ctxt.generateJavaSource("for (int " + tV + "=" + beginV + ";" + tV
					+ ">0 && " + iterV + ".hasNext(); " + tV + "--)");
			ctxt.generateJavaSource(iterV + ".next();");
		}

		ctxt.generateJavaSource("while (" + iterV + ".hasNext()){");
		if (hasVar) {
			ctxt.generateJavaSource("_jspx_page_context.setAttribute(");
			ctxt.generateAttribute("var");
			ctxt.generateJavaSource(", " + iterV + ".next());");
		}

		ctxt.generateBody();

		if (hasStep) {
			String tV = ctxt.getTemporaryVariableName();
			ctxt.generateJavaSource("for (int " + tV + "=" + stepV + "-1;" + tV
					+ ">0 && " + iterV + ".hasNext(); " + tV + "--)");
			ctxt.generateJavaSource(iterV + ".next();");
		}
		if (hasEnd) {
			if (hasStep) {
				ctxt.generateJavaSource(indexV + "+=" + stepV + ";");
			} else {
				ctxt.generateJavaSource(indexV + "++;");
			}
			if (hasBegin) {
				ctxt.generateJavaSource("if(" + beginV + "+" + indexV + ">"
						+ endV + ")");
			} else {
				ctxt.generateJavaSource("if(" + indexV + ">" + endV + ")");
			}
			ctxt.generateJavaSource("break;");
		}
		ctxt.generateJavaSource("}"); // while
	}

	/**
	 * Generate iterators for data types supported in items
	 */
	private void generateIterators(TagPluginContext ctxt) {

		// Object[]
		ctxt.generateDeclaration("ObjectArrayIterator",
				"private Iterator toIterator(final Object[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return a[index++];}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// boolean[]
		ctxt.generateDeclaration("booleanArrayIterator",
				"private Iterator toIterator(final boolean[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Boolean(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// byte[]
		ctxt.generateDeclaration("byteArrayIterator",
				"private Iterator toIterator(final byte[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Byte(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// char[]
		ctxt.generateDeclaration("charArrayIterator",
				"private Iterator toIterator(final char[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Character(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// short[]
		ctxt.generateDeclaration("shortArrayIterator",
				"private Iterator toIterator(final short[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Short(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// int[]
		ctxt.generateDeclaration("intArrayIterator",
				"private Iterator toIterator(final int[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Integer(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// long[]
		ctxt.generateDeclaration("longArrayIterator",
				"private Iterator toIterator(final long[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Long(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// float[]
		ctxt.generateDeclaration("floatArrayIterator",
				"private Iterator toIterator(final float[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Float(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// double[]
		ctxt.generateDeclaration("doubleArrayIterator",
				"private Iterator toIterator(final double[] a){\n"
						+ "  return (new Iterator() {\n" + "    int index=0;\n"
						+ "    public boolean hasNext() {\n"
						+ "      return index < a.length;}\n"
						+ "    public Object next() {\n"
						+ "      return new Double(a[index++]);}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

		// Enumeration
		ctxt.generateDeclaration("enumIterator",
				"private Iterator toIterator(final Enumeration e){\n"
						+ "  return (new Iterator() {\n"
						+ "    public boolean hasNext() {\n"
						+ "      return e.hasMoreElements();}\n"
						+ "    public Object next() {\n"
						+ "      return e.nextElement();}\n"
						+ "    public void remove() {}\n" + "  });\n" + "}");

	}
}
